//Random Game Engine
//Copyright (C) 2013  Nikolay Dionisov

//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "TaskManager.h"
namespace re
{
	namespace core
	{
		Task::Task(TaskId id, TaskId parent, TaskId dep, WorkPtr w) : id_(id), parent_(parent), dependency_(dep), work_(w), references_(1)
		{
		}
		void Task::Execute(TaskManager * const taskman)
		{
			work_->DoWork();
			taskman->TaskCompleted(id_);
		}
		unsigned short Task::GetReferences()
		{
			return references_;
		}

		SessionId TaskManager::StartSession()
		{
			session_mutex.lock();
			SessionId id = GenerateSessionId();
			sessions[id] = TaskList();
			session_mutex.unlock();
			return id;
		}
		//small ut func to help the finish session
		void TaskManager::SortByDependency(std::vector<Task*>& vec)
		{
			if(vec.empty())
			{
				return;
			}
			Task* temp;
			for(unsigned int i = 0; i < vec.size()-1; ++i)
			{
				for(unsigned int u = i+1; u < vec.size(); ++u)
				{
					if(vec[i]->dependency_ == vec[u]->id_)
					{
						temp = vec[i];
						vec[i] = vec[u];
						vec[u] = temp;
					}
				}
			}
		}
		void TaskManager::FinishSession(SessionId id)
		{
			std::vector<Task*> task_vec;
			session_mutex.lock();
			SessionMap::iterator session_iter = sessions.find(id);
			if(session_iter == sessions.end())
			{
				session_mutex.unlock();
				ASSERT(0);
				return;
			}
			//create a list of tasks, populate it and sort it by depencency
			TaskList::iterator task_iter = session_iter->second.begin();
			TaskList::iterator task_end = session_iter->second.end();
			while(task_iter != task_end)
			{
				task_mutex.lock();
				TaskMap::iterator taskmap_iter = tasks.find(*task_iter);
				if(taskmap_iter == tasks.end())
				{
					task_mutex.unlock();
					session_mutex.unlock();
					ASSERT(0);
					return;
				}
				task_vec.push_back(&taskmap_iter->second);
				task_mutex.unlock();
				++task_iter;
			}

			//this session is erased. It other words the following tasks cannot be touched be anything.
			//so there is no need of task lock.
			sessions.erase(session_iter);
			session_mutex.unlock();
			
			SortByDependency(task_vec);

			//push them in open task queue
			std::vector<Task*>::iterator i = task_vec.begin();
			std::vector<Task*>::iterator end = task_vec.end();
			while(i != end)
			{
				opened_tasks_mutex.lock();
				opened_tasks.push_back((*i)->id_);
				condition_var.notify_one();
				opened_tasks_mutex.unlock();
				++i;
			}
		}
		bool TaskManager::UnsafeIsCompleted(TaskId id)
		{
			TaskMap::iterator task_iter = tasks.find(id);
			if(task_iter == tasks.end())
			{
				//no clue about this taks, it's probably completed long ago
				return true;
			}
			if(task_iter->second.GetReferences() == 0)
			{
				//task is still here, it has no references, meaning that all
				//children tasks are done + the parent task itself
				return true;
			}
			return false;
		}
		bool TaskManager::IsCompleted(TaskId id)
		{
			task_mutex.lock();
			bool completed = UnsafeIsCompleted(id);
			task_mutex.unlock();
			return completed;
		}

		TaskId TaskManager::AddTask(WorkPtr work, const SessionId session)
		{
			task_mutex.lock();
			TaskId id = GenerateTaskId();
			Task task(id, 0, 0, work);
			tasks[id] = task;
			task_mutex.unlock();

			session_mutex.lock();
			SessionMap::iterator ses_iter = sessions.find(session);
			if(ses_iter == sessions.end())
			{
				session_mutex.unlock();
				ASSERT(0);
				return 0;
			}
			ses_iter->second.push_back(id);
			session_mutex.unlock();
			return id;
		}
		void TaskManager::SetParent(const SessionId session, const TaskId parent, const TaskId child)
		{
			task_mutex.lock();
			TaskMap::iterator child_iter = tasks.find(child);
			TaskMap::iterator parent_iter = tasks.find(parent);
			if(child_iter == tasks.end() || parent_iter == tasks.end())
			{
				task_mutex.unlock();
				ASSERT(0);
				return;
			}
			if(parent_iter->second.id_ != child_iter->second.parent_)
			{
				parent_iter->second.references_++;
			}
			child_iter->second.parent_ = parent_iter->second.id_;
			task_mutex.unlock();
		}
		void TaskManager::SetDependency(const SessionId session, const TaskId to_exec_first, const TaskId to_exec_second)
		{
			task_mutex.lock();
			TaskMap::iterator first_iter = tasks.find(to_exec_first);
			TaskMap::iterator second_iter = tasks.find(to_exec_second);
			if(first_iter == tasks.end() || second_iter == tasks.end())
			{
				task_mutex.unlock();
				ASSERT(0);
				return;
			}
			second_iter->second.dependency_ = first_iter->second.id_;
			task_mutex.unlock();
		}


		void TaskManager::WaitDoTask()
		{

			Task task;
			
			//opened_tasks.wait_notempty();

			std::unique_lock<std::mutex> lock(opened_tasks_mutex);

			while(opened_tasks.empty())
			{
				condition_var.wait(lock);
			}

			std::list<TaskId>::iterator i = opened_tasks.begin();
			std::list<TaskId>::iterator end = opened_tasks.end();
			TaskMap::iterator task_iter;
			task_mutex.lock();
			while(1)
			{
				//check if i can be processed
				TaskMap::iterator task_iter = tasks.find(*i);
				if(task_iter->second.dependency_ == 0 || UnsafeIsCompleted(task_iter->second.dependency_))
				{
					task = task_iter->second;
					task_mutex.unlock();
					opened_tasks.erase(i);
					break;
				}
				++i;
				if(i == end)
				{
					task_mutex.unlock();
					return;
				}
			}
			lock.unlock();
			task.Execute(this);
		}

		bool TaskManager::TryDoTask()
		{
			Task task;
			std::unique_lock<std::mutex> lock(opened_tasks_mutex);
			if(opened_tasks.empty())
			{
				return false;
			}
			std::list<TaskId>::iterator i = opened_tasks.begin();
			std::list<TaskId>::iterator end = opened_tasks.end();
			TaskMap::iterator task_iter;
			task_mutex.lock();
			while(1)
			{
				//check if i can be processed
				TaskMap::iterator task_iter = tasks.find(*i);

				if(task_iter->second.dependency_ == 0 || UnsafeIsCompleted(task_iter->second.dependency_))
				{
					task = task_iter->second;
					task_mutex.unlock();
					opened_tasks.erase(i);
					lock.unlock();
					break;
				}
				++i;
				if(i == end)
				{
					task_mutex.unlock();
					lock.unlock();
					return false;
				}
			}

			task.Execute(this);
			return true;
		}

		void TaskManager::TaskCompleted(TaskId id)
		{
			task_mutex.lock();
			TaskId working_task = id;
			while(1)
			{
				TaskMap::iterator task_iter = tasks.find(working_task);
				if(task_iter == tasks.end())
				{
					//the task id is missing. it has either been removed or there is an error.
					task_mutex.unlock();
					return;
				}
				task_iter->second.references_--;
				if(task_iter->second.references_ > 0)
				{
					break;
				}
				working_task = task_iter->second.parent_;
				tasks.erase(task_iter);
			}
			task_mutex.unlock();
		}

		TaskId TaskManager::GenerateTaskId()
		{
			TaskId to_return = last_taskid;
			while(1)
			{
				to_return++;
				if(to_return == 0)
				{
					to_return = 1;
				}
				//check if it isn't taken
				TaskMap::iterator task_iter = tasks.find(to_return);
				if(task_iter == tasks.end())
				{
					break;
				}
			}
			last_taskid = to_return;
			return to_return;
		}
		SessionId TaskManager::GenerateSessionId()
		{
			SessionId to_return = last_sessionid;
			while(1)
			{
				to_return++;
				if(to_return == 0)
				{
					to_return = 1;
				}
				//check if it isn't taken
				SessionMap::iterator session_iter = sessions.find(to_return);
				if(session_iter == sessions.end())
				{
					break;
				}
			}
			last_sessionid = to_return;
			return to_return;
		}
	}
}